package nessie;

import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;

/**
 * the main loop of the emulator. the emulator is scanline based. meaning that
 * instructions are only fetched during the H and V blanks(as inaccurate as the
 * cycle counting is, that is).
 */
public class Main {

    private static final Logger logger = Logger.getLogger("Main");
//    private static final int ROM_HEADER_OFFSET = 16;
    private static final int RESET_VECTOR = 0xFFFB;//0xFFFC, but the Memory.ADDRESS_ABSOLUTE() function does pre-increments.
    private static final int NTSC_FRAME_TIME = 1000 / 60;//nano secs
    private static final int PAL_FRAME_TIME = 1000 / 50;//nano secs
    private static int FRAME_TIME = NTSC_FRAME_TIME;
    private static int returnCode = 0;
//    private static boolean NMI = false;//Non Maskable Interrupt
    private static boolean limitedFramerate = true;

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws Exception {
        /**
         * READ ROM
         */
        FileChooser fileChooser = new FileChooser();

//        File file = Paths.get(args[0]).toFile();
        File file = fileChooser.getSelectedFile();
        if (null != file && file.exists() && file.canRead()) {
            logger.log(Level.ALL, "<<< rom length={0} >>>", file.length());
            //BufferedInputStream inputStream = null;
            try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
                Memory.loadRom(inputStream);
            } catch (IOException ex) {
                logger.log(Level.SEVERE, ex.getMessage());
            }

            //init
            JFrame jFrame = new JFrame();
            MainUI mainUI = new MainUI(jFrame);
            CPU cpu = new CPU();
            PPU.init(mainUI);
            Registers.init();
            Palette.init();

            boolean evenFrame = false;

            //reset vector
            Registers.PC = (short) RESET_VECTOR;
            Registers.PC = Memory.ADDRESS_ABSOLUTE();
            PPU.NMI_ME();


            //main loop
            while (true) {
                evenFrame = !evenFrame;
                long tiem = System.currentTimeMillis();

                try {
                    {//frame
                        for (int currentScanline = 0; currentScanline < 257; currentScanline++) {
                            cpu.cyclone = 0;
                            //HBLANK loop
                            while ((cpu.cyclone * 3) < 340) {//340 ppu cycles per scanline, 1 cpu cycle = 3 ppu cycles
                                cpu.exec(Memory.readByte(Registers.getUnsignerPC()));
                            }
                            PPU.draw(currentScanline);
                        }

                        {//let the cpu catch up to the ppu
                            while (PPU.isVBL() && (cpu.cyclone * 3) < 340 * 21) {//catch up
                                cpu.exec(Memory.readByte(Registers.getUnsignerPC()));
                            }
                        }
                    }

                    //limit frame rate
                    while (limitedFramerate && tiem + FRAME_TIME > System.currentTimeMillis());
                } catch (Exception ex) {
                    logger.log(Level.SEVERE, ex.getMessage());
                    ex.printStackTrace();
                    returnCode = 1;
                    break;
                }
            }

        } else {
            returnCode = 1;
            if (null == file) {
                System.err.println("invalid selection");
            } else if (!file.exists()) {
                System.err.println("could not find " + file.getAbsolutePath());
            } else {
                System.err.println("could not open " + file.getAbsolutePath());
            }
        }
        System.exit(returnCode);
    }

    /**switches from PAL to NTSC and vice versa*/
    public static void switchModes() {
        //TODO:add autodetect mode
        if (FRAME_TIME == NTSC_FRAME_TIME) {
            FRAME_TIME = PAL_FRAME_TIME;
        } else {
            FRAME_TIME = NTSC_FRAME_TIME;
        }
    }

    /**enables/disables framerate limit*/
    public static void switchFramerateLimit() {
        limitedFramerate = !limitedFramerate;
    }
    /**
     * <editor-fold defaultstate="collapsed" desc="preliminary analysis"> ******
     */
    /*
     ***************
     * M E M O R Y *
     ***************
     *
     * little endian 64kb memory $0000-$FFFF zero page $0000-$00FF three
     * mirrors: $0000-$07FF $0800-$0FFF $1000-$17FF $1800-$1FFF
     *
     * I/O registers $2000-$401F mirrored every 8 bytes?
     *
     *
     * $8000+ ROM data 16KB rom banks @ $8000 and $C000 if only one then mirror
     * @ $8000 and $C000 more than 2 use memmory mapper
     *
     *
     * * *S T A C K: $0100-$01FF
     *
     *
     * * *
     *
     */
    /*
     *********************
     * R E G I S T E R S *
     *********************
     * PC(Program Counter) 16bit, holds next instruction address
     *
     * SP(Stack Pointer) 8bit, top down??? NO OVERFLOW, wraparound from $00 to
     * $FF
     *
     * A(Accumulator) 8bit, stores arithmetic results or memory values
     *
     * X(index register X) 8bit, spritesPerScanlineCounter or offset or memory
     * value, can set or get SP
     *
     * Y(index register Y) 8bit, same as X but no SP influence
     *
     * P(Processor status) 8bit, flags register
     *
     */
    /*
     ***********************
     * I N T E R R U P T S *
     ***********************
     *
     * NMI_VBL IRQ reset
     */
    /*
     ***********************************
     * A D D R E S S I N G M O D E S *
     ***********************************
     */
    /*
     ******************************************
     * I N S T R U C T I O N S / O P C O D E S*
     ******************************************
     *
     * 56 differenct opcodes some have variations in differenct addressing modes
     * total including variations is 151 out of 256????????
     *
     *
     * opcodes can be 1, 2 or 3 bytes long!!!!!!!
     *
     * instruction categories: -load/store -register transfer -stack op -logical
     * op -arithmetic op -inc/dec -shifts -jmp -branch -status reg(flags)
     * -system functions
     */
    /*
     *********
     * P P U *
     *********
     *
     * registers located within CPU mem $2000-$2007 $4014(DMA) special scrolling
     * regs
     *
     * can address 64kb VRAM only has 16KB
     *
     * uses 16bit addesses
     *
     * exec/write to $2006 & $2007 during V-BLANK
     *
     * SPR-RAM = 256B
     *
     * PPU register 1 $2000 (bit 7 can disable NMI_VBL, bit 5 to switch to 8x16,
     * bit 2 for horizontal(1) or vertical(32) movement) PPU register 2 $2001
     * (bit 3 hide background, bit 4 hide sprites) PPU status register $2002
     * (exec only, bit 7 V-BLANK, clear bit 7 on raed($2006 & $2006 too))
     *
     *
     ****DMA: write sprite address x $100 to $4014??????????????????? the
     * memory bus is unavailable during a DMA write(cycle stealing)
     *
     *
     ****color palettes: image palette $3F00-$3F0F(52 colors) for background
     * sprite palette $3F10-$3F1F(" ") for sprites
     *
     * $3F00 is used for transparent background palettes hold color index for
     * system palette. max 25 colors at a time.
     *
     *
     *
     ****pattern tables: pattern table $0000 pattern table $1000
     *
     * not all games use these RAM tables, some have patterns embedded on the
     * ROM. 00b 01b 10b 11b
     *
     *
     ****name tables: 2KB 32x30 tiles (1 tile==8x8 picsels) each name table has
     * an attribute table
     *
     ***attribute table: 1 byte = 4x4 tiles(2 bits per tile..upper two bits)
     * each 4x4 group is divided into four 2x2
     * squares(square0=0,1,2,3,....square3=C,D,E,F) MIRRORING!!!!!!!!!!!!!!
     *
     *
     *
     *
     ****SPRITES: 8x8 16x16 sprite data in pattern table SPR-RAM max 64 sprites
     * 4 bytes wide byte 0=y coordinate(top left minus 1) byte 1=sprite
     * index(patt tab)(even-$0000, odd=$1000) byte 2= bit0-1 =most sig color
     * bit5 =z depth(boolean) bit6 =flip hor bit7 =flip ver byte 3=
     *
     * 8 sprites per scanline
     */
    /**
     * **i N E S 16byte header 0=N 1=E 2=S 3=#PRG-ROM banks 4=#CHR-ROM
     * banks(VROM) 5=Control byte 1 6=Control byte 2 7=#RAM banks(0=1) 8= 9= A=
     * B= C= D= E= F=
     *
     */
    /**
     * C O N T R O L L E R
     * memory mapped IOPORTS: controller 1 $4016 controller 2 $4017 8 reads per
     * controller(A,B,Select,Start,U,D,L,R)
     *
     */
    /**
     * ******
     * </editor-fold>
     */
}
